<?php
/**
 * 문자열 유틸리티 클래스
 *
 * @package php.lang
 */


/**
 * 문자열 유틸리티 클래스
 *
 * 매칭테이블 
 * <pre>
 * String 메소드		=>	문자열함수
 * addcslashes	=>	addcslashes
 * addslashes		=>	addslashes
 * bin2hex		=>	bin2hex
 * chop			=>	chop
 * chr		=>	chr
 * count_chars		=>	count_chars
 * entity_encode		=>	htmlentities
 * entity_decode		=>	html_entity_decode
 * explode		=>	explode
 * implode		=>	implode
 * join		=> join
 * ltrim		=>	ltrim
 * md5		=>	md5
 * nl2br		=>	nl2br
 * ord		=>	ord
 * parse		=>	parse_str
 * print		=>	print
 * printf		=>	printf
 * rtrim		=>	rtrim
 * similar		=>	similar_text
 * soundex		=>	soundex
 * sprintf		=>	sprintf
 * sscanf		=>	sscanf
 * ireplace		=>	str_ireplace
 * pad		=>	str_pad
 * repeat		=>	str_repeat
 * replace		=>	str_replace
 * rot13		=>	str_rot13
 * shuffle		=>	str_shuffle
 * count_split		=>	str_split
 * word_count		=>	str_word_count
 * casecmp		=>	strcasecmp
 * cmp		=>	strcmp
 * coll		=>	strcoll
 * strip		=>	strip_tags
 * stripcslashes		=>	stripcslashes
 * ipos		=>	stripos
 * stripslashes		=>	stripslashes
 * istr		=>	stristr
 * len		=>	strlen
 * natcasecmp		=>	strnatcasecmp
 * natcmp 		=>	strnatcmp
 * ncasecmp 		=>	strncasecmp
 * ncmp		=>	strncmp
 * pbrk		=>	strpbrk
 * pos		=>	strpos
 * last_string		=>	strrchr
 * reverse 		=>	strrev
 * reverse_ipos		=>	strripos
 * reverse_pos		=>	strrpos
 * first_string		=>	strstr
 * lower		=>	strtolower
 * upper		=>	strtoupper
 * translate 		=>	strtr
 * sub_compare		=>	substr_compare
 * sub_count 		=>	substr_count
 * sub_replace		=>	substr_replace
 * substring 		=>	substr
 * trim		=>	trim
 * ucfirst 		=>	ucfirst
 * ucwords 		=>	ucwords
 * wrap		=>	wordwrap
 * iconv		=>	iconv
 * base64_encode		=>	base64_encode
 * base64_decode		=>	base64_decode
 * build_query		=>	http_build_query
 * parse_url		=>	parse_url
 * rawencode		=>	rawurlencode
 * rawdecode		=>	rawurldecode
 * urlencode		=>	urlencode
 * urldecode		=>	urldecode
 * highlight		=>	highlight_string
 * phpstrip		=>	php_strip_whitespace
 * constant		=>	constant
 * split		=>	split
 * spliti		=>	spliti
 * number		=>	number_format
 * money		=>	money_format
 * pmatch_all		=>	preg_match_all
 * pmatch		=>	preg_match
 * preg_qoute		=>	preg_qoute
 * preplace_callback		=>	preg_replace_callback
 * preplace		=>	preg_replace
 * psplit		=>	preg_split
 * </pre>
 * 
 * 
 *
 * @package php.lang
 * @version 1.0
 *
 */
class String extends PObject
{
	private $source = "";
	private $current = "";

	private $history;
	private $is_ref = false;
	private $return;


	/** 
	 * 매개변수로 적용 안함 
	 */
	const POS_ZERO	= -1;			

	/** 
	 * 첫번째 매개변수 
	 */
	const POS_FIRST = 0;			

	/** 
	 * 두번째 매개변수 
	 */
	const POS_SECOND = 1;			

	/** 
	 * 세번째 매개변수 
	 */
	const POS_THIRD = 2;		

	/** 
	 * 마지막 매개변수 
	 */
	const POS_MAX	= 1000;			

	// 기본 함수 테이블 
	private $function_list = array();


	public function __construct($string = '') { 
		$this->source	= $string;
		$this->current	= $string;

		$this->init();
	}

	public function init() { 

		// 내부 메소드 호출 
		$this->setFunctionList('before',	array($this, '_before'),	self::POS_ZERO);
		$this->setFunctionList('append',	array($this, '_append'),	self::POS_ZERO);
		$this->setFunctionList('left',		array($this, '_left'),		self::POS_ZERO);
		$this->setFunctionList('right',		array($this, '_right'),	self::POS_ZERO);
	}

	/**
	 * string 관련함수 자동실행 
	 *
	 * @return String 
	 */
	public function __call($method, $args) { 

		$method = strtolower($method);

		if (! $this->isFunction($method)) return  ;

		// String 객체가 있으면 array로 변환시킴 
		foreach ($args as $key => $value) { 
			$args[$key] = PObject::getObjValue($value);
		}

		// 자기자신이 포함이 되면 매개변수 재작성 

		if ($this->getPos($method) > self::POS_ZERO) { 
			$this->createMethodArgs($method, $args);	
		}

		// 메소드 실행 

		$this->return = call_user_func_array($this->getFunction($method), $args);

		// 참조 형태이고 is_reference 속성이 false 이면 
		// 즉, 현재 return 값을 current 로 바꿈 
		if ($this->is_ref) { 
			$this->current = $this->return;
		}

		// 참조 초기화 
		$this->ref(false);

		return $this;
	}


	/**
	 * 적용될 함수 설정 
	 *
	 * @param string $name 함수이름
	 * @param string|array $run callback 
	 * @param int $pos 매개변수 위치
	 */
	public function setFunctionList($name, $run, $pos) { 
		$this->function_list[$name] = array('function' => $run,	'pos' => $pos);
	}

	public function isFunction($name) { 
		return  (array_key_exists($name, $GLOBALS['FUNCTION_TABLE']['STRING']) or array_key_exists($name, $this->function_list));
	}

	public function __get($key) { 

		switch (strtolower($key)) { 
			case 'return':	return $this->return;
			case 'to':		return $this->current;
			case 'source':	return $this->source;
			case 'object':	return (is_array($this->return) ? A_($this->return) : new String($this->return));
		}

		return '';
	}

	/**
	 * 현재 참조상태 전환
	 *
	 * @param bool $is_ref 참조 상태, true이면 무조건 참조 적용, 기본값 true, 
	 * @return String 
	 * @see String::r()
	 */
	public function ref($is_ref = true) { 
		$this->is_ref = $is_ref;

		return $this;
	}

	/**
	 * 참조가 아닌 상태 함수를 쓸 때 자동으로 참조형태로 전환 
	 *
	 * @return String 
	 * @see String::ref()
	 */
	public function r() { 
		$this->ref();

		$arr = func_get_args();
		$func = array_shift($arr);

		return call_user_func_array(array($this, $func), $arr);

	}


	/** 
	 * 매개변수 위치 얻기
	 *
	 * @param string $method  실행될 함수 이름 
	 * @return int    매개변수 위치 
	 *
	 */
	public function getPos($method) { 

		if (array_key_exists($method, $GLOBALS['FUNCTION_TABLE']['STRING'])) { 
			return $GLOBALS['FUNCTION_TABLE']['STRING'][$method]['pos'];
		} else { 
			return $this->function_list[$method]['pos'];
		}
	}
	
	/** 
	 * 실제 실행될 함수 
	 * 
	 * @param string $method  실행될 함수 이름 
	 * @return callback   콜백이름 
	 */
	public function getFunction($method) { 
		return D_($GLOBALS['FUNCTION_TABLE']['STRING'][$method]['function'] , $this->function_list[$method]['function']);
	}


	/**
	 * 매개변수 정렬 
	 *
	 * 들어온 매개변수와 current 변수를 합쳐서 실제 적용될 매개변수 리스트를 반환한다. 
	 *
	 * @param string $method 실행될 함수 이름 
	 * @param array $args 적용될 매개변수 
	 */
	private function createMethodArgs($method, &$args) { 
		$pos = $this->getPos($method);

		$left = array_slice($args, 0, $pos);
		$right = array_slice($args, $pos);

		if ($pos == self::POS_MAX) { 
			$args[] = $this->current;
		}  else { 
			$args = array_merge($left, array($this->current), $right);
		}
	}

	/**
	 * 문자열 자르기 (수정 필요)
	 * php_mbstring.dll 로드 필요 
	 * 
	 * @param	string	$str	문자열
	 * @param	int		$length 자르는 길이
	 * @param	string	$subfix	뒤에 붙을 문자열, 기본값 "..."
	 * @return	string
	 *
	 */
	public static function cut($str, $length, $subfix = "...") {
		if (strlen($str) <= $length) { 
			return $str;
		} else { 
			return substr($str, 0, $length).$subfix;
		}	
	}

	/**
	 * UTF-8 형태로 문자열 변환  (php5는 iconv 모듈 기본 탑재)
	 * 
	 * @param	string $str		변환될 문자열
	 * @param	string $encoding 이전 인코딩
	 * @return	string
	 *
	 */
	public static function getUTF8($str, $encoding = 'CP949') { 
		return iconv($encoding, 'utf-8', $str);
	}

	/**
	 * EUC-KR 형태로 문자열 변환  (php5는 iconv 모듈 기본 탑재)
	 * 
	 * @param	string	$str	  변환될 문자열
	 * @param	string 	$encoding 이전 인코딩
	 * @return	string
	 *
	 */

	public static function getEUCKR($str, $encoding = 'UTF-8') { 
		return iconv($encoding, 'euc-kr', $str);
	}

	/**
	 * 태그 비교, 생성 
	 * 
	 * @param	string	$tagName  태그 이름
	 * @param	string	$subject  검색될 문자열
	 * @param	boolean	$isOne	  true : One 태그, false : Two 태그
	 *
	 * @return	array	  tag			: 태그 이름,
	 *					  body			: 매칭 문자열,
	 *					  id			:  아이디,
	 *					  attribute		: 속성,
	 *					  description	: 내용,
	 *					  convert		: 변환 내용
	 *
	 */
	public static function matchTag($tagName, $subject, $isOne = true) {
		$tagName = strtolower($tagName);

		if ($isOne) { 
			$pattern = "/(\\[{$tagName}(=([^ ]+))? ([^\\]]+)\\])+/i";
		} else { 
			$pattern = "/(\\[{$tagName}(=([^\\] ]+))?([^\\]]*)\\]([^\\a]*)\\[\\/{$tagName}\\])+/i";
		}

		preg_match_all($pattern, $subject, $out, PREG_SET_ORDER);

		$arr = array();

		foreach ($out as $obj) {
			$arr[] = array(
						'tag'			=> $tagName,		// 태그 이름
						'body'			=> $obj[1],			// 매칭 문자열
						'id'			=> $obj[3],			// 아이디
						'attribute'		=> $obj[4],			// 속성
						'description'	=> $obj[5],			// 내용
						'convert'		=> $obj[5]			// 변환 내용
			);
		}

		return $arr;
	}

	/**
	 * 태그 생성
	 *
	 * callback 함수는 기본적으로 attribute, description 을 이용해서 convert를 생성하는 함수이다. 
	 * 기본 형태는 
	 *
	 *  <code>
	 *  function callback_func(&$obj) { 
	 *		$obj['convert'] = str_replace("\n", "<br>", $obj['description']);
	 *	} </code>
	 *
	 *  
	 *  callback 함수가 클래스의 메소드라면 
	 * 
	 *  <code>
	 *  class AAA { 
     *     function callback_func(&$obj) { 
     *
	 *		}
	 *	}
	 *  
	 *  $class = new AAA;
	 *  array($class, 'callback_func'); 
	 *  </code>
	 * 
	 * 	 해서 callback 함수 부분에 넣어 주면 된다.
	 *
	 * @param	string	$tagName	태그 이름
	 * @param	string	$subject	검색될 문자열
	 * @param	boolean	$isOne		true : One 태그, false : Two 태그
	 * @param	callback	$callback	callback 함수
	 *
	 * @return	array	  tag			: 태그 이름,
	 *					  body			: 매칭 문자열,
	 *					  id			:  아이디,
	 *					  attribute		: 속성,
	 *					  description	: 내용,
	 *					  convert		: 변환 내용
	 *
	 */
	public static function matchTagCallBack($tagName, $subject, $isOne, $callback) { 
		if ($callback == null) return;

		$arr = String::matchTag($tagName, $subject, $isOne);

		if ($callback != null) { 
			foreach ($arr as &$obj) { 
				$obj = call_user_func($callback, $obj);
			}
		} 

		return $arr;
	}

	/**
	 * 문자열 변환 
	 * 
	 * @param	string	$tagName  태그 이름
	 * @param	string	$subject  검색될 문자열
	 * @param	boolean	$isOne	  true : One 태그, false : Two 태그
	 * @param	callback $callback callback 함수
	 * @return	string	변환된 $subject 		
	 */
	public static function matchTagReplace($tagName, $subject, $isOne, $callback)  { 
		
		$arr = String::matchTagCallBack($tagName, $subject, $isOne, $callback);

		foreach ($arr as $obj) { 
			$subject = str_replace($obj['body'].PHP_EOL, $obj['convert'], $subject);
			$subject = str_replace($obj['body'], $obj['convert'], $subject);
		}

		return $subject;
	}

	private function _before($string = '') { 
		return $string.$this->current;
	}

	private function _append($string = '') { 
		return $this->current.$string;
	}

	private function _left($len) { 
		return substr($this->current, 0, $len);
	}

	private function _right($len) { 
		return substr($this->current, strlen($this->current) - $len, $len-1);
	}

	public function toString() { 
		return $this->current;
	}
}


$GLOBALS['FUNCTION_TABLE']['STRING'] = array (
	'addcslashes'	=> array('function' => 'addcslashes',	'pos' => String::POS_FIRST),
	'addslashes'	=> array('function' => 'addslashes',	'pos' => String::POS_FIRST),
	'bin2hex'		=> array('function' => 'bin2hex',		'pos' => String::POS_FIRST),
	'chop'			=> array('function' => 'chop',			'pos' => String::POS_FIRST),
	'chr'			=> array('function' => 'chr',			'pos' => String::POS_FIRST),
	'count_chars'	=> array('function' => 'count_chars',	'pos' => String::POS_FIRST),
	'entity_encode'	=> array('function' => 'htmlentities',	'pos' => String::POS_FIRST),
	'entity_decode'	=> array('function' => 'html_entity_decode',	'pos' => String::POS_FIRST),
	'explode'		=> array('function' => 'explode',		'pos' => String::POS_SECOND),
	'implode'		=> array('function' => 'implode',		'pos' => String::POS_ZERO),
	'join'			=> array('function' => 'join',			'pos' => String::POS_ZERO),
	'ltrim'			=> array('function' => 'ltrim',			'pos' => String::POS_FIRST),
	'md5'			=> array('function' => 'md5',			'pos' => String::POS_FIRST),
	'nl2br'			=> array('function' => 'nl2br',			'pos' => String::POS_FIRST),
	'ord'			=> array('function' => 'ord',			'pos' => String::POS_FIRST),
	'parse'			=> array('function' => 'parse_str',		'pos' => String::POS_FIRST),
	'print'			=> array('function' => 'print',			'pos' => String::POS_FIRST),
	'printf'		=> array('function' => 'printf',		'pos' => String::POS_FIRST),
	'rtrim'			=> array('function' => 'rtrim',			'pos' => String::POS_FIRST),
	'similar'		=> array('function' => 'similar_text',	'pos' => String::POS_FIRST),
	'soundex'		=> array('function' => 'soundex',		'pos' => String::POS_FIRST),
	'sprintf'		=> array('function' => 'sprintf',		'pos' => String::POS_FIRST),
	'sscanf'		=> array('function' => 'sscanf',		'pos' => String::POS_SECOND),
	'ireplace'		=> array('function' => 'str_ireplace',	'pos' => String::POS_FIRST),
	'pad'			=> array('function' => 'str_pad',		'pos' => String::POS_FIRST),
	'repeat'		=> array('function' => 'str_repeat',	'pos' => String::POS_FIRST),
	'replace'		=> array('function' => 'str_replace',	'pos' => String::POS_THIRD),
	'rot13'			=> array('function' => 'str_rot13',		'pos' => String::POS_FIRST),
	'shuffle'		=> array('function' => 'str_shuffle',	'pos' => String::POS_FIRST),
	'count_split'	=> array('function' => 'str_split',		'pos' => String::POS_FIRST),
	'word_count'	=> array('function' => 'str_word_count','pos' => String::POS_FIRST),
	'casecmp'		=> array('function' => 'strcasecmp',	'pos' => String::POS_FIRST),
	'cmp'			=> array('function' => 'strcmp',		'pos' => String::POS_FIRST),
	'coll'			=> array('function' => 'strcoll',		'pos' => String::POS_FIRST),
	'strip'			=> array('function' => 'strip_tags',	'pos' => String::POS_FIRST),
	'stripcslashes'	=> array('function' => 'stripcslashes',	'pos' => String::POS_FIRST),
	'ipos'			=> array('function' => 'stripos',		'pos' => String::POS_FIRST),
	'stripslashes'	=> array('function' => 'stripslashes',	'pos' => String::POS_FIRST),
	'istr'			=> array('function' => 'stristr',		'pos' => String::POS_FIRST),
	'len'			=> array('function' => 'strlen',		'pos' => String::POS_FIRST),
	'natcasecmp'	=> array('function' => 'strnatcasecmp',	'pos' => String::POS_FIRST),
	'natcmp'		=> array('function' => 'strnatcmp',		'pos' => String::POS_FIRST),
	'ncasecmp'		=> array('function' => 'strncasecmp',	'pos' => String::POS_FIRST),
	'ncmp'			=> array('function' => 'strncmp',		'pos' => String::POS_FIRST),
	'pbrk'			=> array('function' => 'strpbrk',		'pos' => String::POS_FIRST),
	'pos'			=> array('function' => 'strpos',		'pos' => String::POS_FIRST),
	'last_string'	=> array('function' => 'strrchr',		'pos' => String::POS_FIRST),
	'reverse'		=> array('function' => 'strrev',		'pos' => String::POS_FIRST),
	'reverse_ipos'	=> array('function' => 'strripos',		'pos' => String::POS_FIRST),
	'reverse_pos'	=> array('function' => 'strrpos',		'pos' => String::POS_FIRST),
	'first_string'	=> array('function' => 'strstr',		'pos' => String::POS_FIRST),
	'lower'			=> array('function' => 'strtolower',	'pos' => String::POS_FIRST),
	'upper'			=> array('function' => 'strtoupper',	'pos' => String::POS_FIRST),
	'translate'		=> array('function' => 'strtr',			'pos' => String::POS_FIRST),
	'sub_compare'	=> array('function' => 'substr_compare','pos' => String::POS_FIRST),
	'sub_count'		=> array('function' => 'substr_count',	'pos' => String::POS_FIRST),
	'sub_replace'	=> array('function' => 'substr_replace','pos' => String::POS_FIRST),
	'substring'		=> array('function' => 'substr',		'pos' => String::POS_FIRST),
	'trim'			=> array('function' => 'trim',			'pos' => String::POS_FIRST),
	'ucfirst'		=> array('function' => 'ucfirst',		'pos' => String::POS_FIRST),
	'ucwords'		=> array('function' => 'ucwords',		'pos' => String::POS_FIRST),
	'wrap'			=> array('function' => 'wordwrap',		'pos' => String::POS_FIRST),
	'iconv'			=> array('function' => 'iconv',			'pos' => String::POS_THIRD),
	'base64_encode'	=> array('function' => 'base64_encode',	'pos' => String::POS_FIRST),
	'base64_decode'	=> array('function' => 'base64_decode',	'pos' => String::POS_FIRST),
	'build_query'	=> array('function' => 'http_build_query',	'pos' => String::POS_ZERO),
	'parse_url'		=> array('function' => 'parse_url',		'pos' => String::POS_FIRST),
	'rawencode'		=> array('function' => 'rawurlencode',	'pos' => String::POS_FIRST),
	'rawdecode'		=> array('function' => 'rawurldecode',	'pos' => String::POS_FIRST),
	'urlencode'		=> array('function' => 'urlencode',		'pos' => String::POS_FIRST),
	'urldecode'		=> array('function' => 'urldecode',		'pos' => String::POS_FIRST),
	'highlight'		=> array('function' => 'highlight_string',	'pos' => String::POS_FIRST),
	'phpstrip'		=> array('function' => 'php_strip_whitespace',	'pos' => String::POS_FIRST),
	'constant'		=> array('function' => 'constant',		'pos' => String::POS_FIRST),
	'split'			=> array('function' => 'split',			'pos' => String::POS_SECOND),
	'spliti'		=> array('function' => 'spliti',		'pos' => String::POS_SECOND),
	'number'		=> array('function' => 'number_format',	'pos' => String::POS_FIRST),
	'money'			=> array('function' => 'money_format',	'pos' => String::POS_FIRST),
	'pmatch_all'	=> array('function' => 'preg_match_all',	'pos' => String::POS_SECOND),
	'pmatch'		=> array('function' => 'preg_match',	'pos' => String::POS_SECOND),
	'preg_qoute'	=> array('function' => 'preg_qoute',	'pos' => String::POS_SECOND),
	'preplace_callback'	=> array('function' => 'preg_replace_callback',	'pos' => String::POS_THIRD),
	'preplace'		=> array('function' => 'preg_replace',	'pos' => String::POS_THIRD),
	'psplit'		=> array('function' => 'preg_split',	'pos' => String::POS_SECOND),
);

?>